/*
 * @Descripttion: // 作业二 —— 加载模型基础上 加上Phong光照
 * @version: 
 * @Author: JinYiGao
 * @Date: 2021-08-25 22:51:44
 * @LastEditors: JinYiGao
 * @LastEditTime: 2021-08-30 13:21:25
 */

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>
#include <fstream>
#include <string>

#include <utils/shader.h>
#include <utils/model.h>
#include <utils/camera.h>

using namespace std;

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void mouse_button_callback(GLFWwindow *window, int button, int action, int mods);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

Camera *camera; // 相机
Eigen::Vector3f lightPos(1.2f, 1.0f, 2.0f); // 光源位置

int main()
{
    system("chcp 65001");
    // glfw: 初始化
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw 窗体创建
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
	glfwSetScrollCallback(window, scroll_callback);
	glfwSetMouseButtonCallback(window, mouse_button_callback);
	glfwSetCursorPosCallback(window, mouse_callback);

    // glad: 加载OpenGL函数指针
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

	// ------------------------------------------------------------------
	// ------------------------------------------------------------------
	stbi_set_flip_vertically_on_load(true);
	// 相机
	camera = new Camera();
	camera->arcball.setSize(Vector2i(SCR_WIDTH, SCR_HEIGHT));

	// OpenGL 初始化设置
	glEnable(GL_DEPTH_TEST); // 开启深度测试

	// 着色器创建
    // 在调试时，需要将 VS 工程上的 Working Directory 设为 bin 目录，才能正确识别相对路径
	Shader *lightShader = new Shader();
	lightShader->createFromFileName("./shader/light_cube.vs", "./shader/light_cube.fs"); // 懒得画灯了(原理上给定光照位置就好了) 想画的可以自己尝试一下......
	Shader *modelShader = new Shader();
	modelShader->createFromFileName("./shader/phong_model.vs", "./shader/phong_model.fs");

	// 模型加载
	//const string path = "./model/nanosuit/nanosuit.obj";
    // 在调试时，需要将 VS 工程上的 Working Directory 设为 bin 目录，才能正确识别相对路径
	string path = "../model/bun_zipper.ply";
	Model bunnyModel(path);

    // 循环渲染
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        processInput(window);

        // render
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		modelShader->bind();
		// modelShader Uniform变量赋值
		modelShader->setVec3("objectColor", 1.0f, 0.5f, 0.31f);
		modelShader->setVec3("lightColor", 1.0f, 1.0f, 1.0f);
		modelShader->setVec3("lightPos", lightPos);
		modelShader->setVec3("viewPos", camera->position);

		auto mvp = camera->get_mvp();
		auto model = std::get<0>(mvp);
		auto view = std::get<1>(mvp);
		auto proj = std::get<2>(mvp);
		modelShader->setMat4("model", model);
		modelShader->setMat4("view", view);
		modelShader->setMat4("projection", proj);

		// 绘制模型 
		bunnyModel.Draw(modelShader);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

// 用户鼠标键盘交互 回调函数
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);

	camera->size = { width, height };
	camera->arcball.setSize(Vector2i(width, height));
}

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
	//std::cout << xpos << std::endl;
	double x, y;
	if (!camera->is_rotate && !camera->is_translate) { 
		return;
	}
	else if (camera->is_rotate) {
		std::cout << "Rotation..." << std::endl;
		glfwGetCursorPos(window, &x, &y);
		Eigen::Vector2i screen((int)x, (int)y);
		camera->motion_rotate(screen);
	}
	else if (camera->is_translate) {
		std::cout << "Translation..." << std::endl;
		glfwGetCursorPos(window, &x, &y);
		Eigen::Vector2f screen(x, y);
		camera->motion_translate(screen);
	}
}

void mouse_button_callback(GLFWwindow *window, int button, int action, int mods) {
	//左键按下
	if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS)
	{
		std::cout << "LeftButton Press..." << std::endl;
		camera->is_rotate = true;
		double x, y;
		glfwGetCursorPos(window, &x, &y);
		Eigen::Vector2i screen((int)x, (int)y);
		camera->start_rotate(screen); //开始旋转
	}
	//中键按下
	if (button == GLFW_MOUSE_BUTTON_MIDDLE && action == GLFW_PRESS)
	{
		std::cout << "MiddleButton Press..." << std::endl;
		camera->is_translate = true;
		double x, y;
		glfwGetCursorPos(window, &x, &y);
		Eigen::Vector2f screen(x, y);
		camera->start_translate(screen); //开始平移
	}
	// 鼠标抬起
	if (action == GLFW_RELEASE)
	{
		std::cout << "Mouse Release..." << std::endl;
		double x, y;
		glfwGetCursorPos(window, &x, &y);
		Eigen::Vector2i screen((int)x, (int)y);
		camera->end_rotate(screen);
		camera->end_translate(screen); //结束平移
	}
}

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
	//通过改变相机视野 来缩放 效果好 但是需要根据模型包围盒计算相机应该在的位置，不然会出现 一些奇怪的现象...
	camera->view_angle = std::max(0.001, camera->view_angle * (yoffset < 0 ? 1.1 : 0.9));
	camera->view_angle = std::min(1000.0f, camera->view_angle);
}